package com.kamikaze.docidset.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.ListIterator;

import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.OpenBitSet;

import com.kamikaze.docidset.bitset.MyOpenBitSet;
import com.kamikaze.docidset.compression.P4DSetNoBase;

/**
 * Doc id set wrapper around P4DSet
 * 
 * 
 * @author abhasin
 * 
 */
public class P4DDocIdSet extends AbstractDocSet implements Serializable
{

    private static final long serialVersionUID = 1L;
  
	/** 
	 * Utitlity Object compression.
	 */ 
	private P4DSetNoBase compressedSet = new P4DSetNoBase();
	
	/**
	 * List for the base integer values of the compressed batches.
	 */ 
	private ArrayList<Integer> baseList = null;

	public P4DDocIdSet(int batchSize) 
	{
		baseList = new ArrayList<Integer>();
		this.BATCH_SIZE = batchSize;
		this.BATCH_OVER = batchSize / 10;
	}

	@Override
	protected Object compress() 
	{
		current[0] = 0;
		compressedSet.setParam(current_base, current_b, BATCH_SIZE,
				current_ex_count);
		baseList.add(current_base);
		return compressedSet.compress(current);
	}

	
	/**
	 * Method to decompress the entire batch
	 * @param blob MyOpenBitSet
	 * @return int array with decompressed segment of numbers
	 */
	protected int[] decompress(MyOpenBitSet blob) 
	{
		return new P4DSetNoBase().decompress(blob);
	}

	/** Binary search
	 * 
	 * @param val
	 * @param begin
	 * @param end
	 * @return index greater than or equal to the target. -1 if the target is out of range.
	 */
	protected int binarySearchForNearest(int val, int begin, int end) 
	{
		if(baseList.size()==0)
			return -1;
		int mid = (begin + end) / 2;
		if (mid == end || (baseList.get(mid) <= val && baseList.get(mid + 1) > val))
			return mid;
		// else if (array.get(mid) == val)
		// return mid;
		else if (baseList.get(mid) < val)
			return binarySearchForNearest(val,  mid + 1, end);
		else
			return binarySearchForNearest(val,  begin, mid);
	}

	
	class P4DDocIdSetIterator extends DocIdSetIterator implements Serializable
	{
	  
	    private static final long serialVersionUID = 1L;

		/**
		 * Address bits
		 * 
		 */
		int ADDRESS_BITS = (int) (Math.log(BATCH_SIZE) / Math.log(2));

		/**
		 * retaining Offset from the list of blobs from the iterator pov
		 * 
		 */
		int cursor = -1;

		/**
		 * Current iterating batch index.
		 * 
		 */
		int bi = 0;

		/**
		 * Current iterating offset.
		 * 
		 */
		int offset = 0;

		/**
		 * doc() returned
		 * 
		 */
		int lastReturn = -1;

		/**
		 * size of the set
		 * 
		 */
		int size = size();

		/**
		 * Reference to the blob iterating
		 * 
		 */
		MyOpenBitSet ref = null;

		/**
		 * Reference to the blob iterating
		 * 
		 */
		int blobSize = blob.size();

		/**
		 * The modCount value that the iterator believes that the backing List
		 * should have. If this expectation is violated, the iterator has
		 * detected concurrent modification.
		 */
		int expectedModCount = modCount;

		/**
		 * Iterators for the lists
		 */
		ListIterator<Integer> blit = baseList.listIterator();

		/**
		 * Iterators for the lists
		 */
		ListIterator<MyOpenBitSet> blobit = blob.listIterator();

		P4DDocIdSetIterator() {
			super();
			lastReturn = blobSize > 0 ? baseList.get(0) : 0;
			ref = blobSize > 0 ? blob.get(0) : null;

		}

		@Override
		public int doc()
		{
			return lastReturn;
		}

		/**
		 * Method to allow iteration in decompressed form
		 */ 
		public int get(OpenBitSet set, int index) 
		{
			return compressedSet.get(set, index);
		}

		@Override
		public boolean next() 
		{
			// increment the cursor and check if it falls in the range for the
			// number of batches, if not return false else, its within range
			if(++cursor < size) 
			{
      		
				
      			// We are already in the array
                  if (bi == blobSize)
                  {
                	  if(offset == -1)
                	  {
                		  lastReturn  = INVALID;
                	  	  return false;
                	  }
                	  else
                		  lastReturn += current[offset++];
                  }
                  // if we are not in the array but on the boundary of a batch
                  // update local blob and set params
                  else if (offset == 0) {
      
                      bi = batchIndex(cursor);
      
                      if (bi < blobSize) {
                          lastReturn = blit.next();
                          ref = blobit.next();
                          compressedSet.updateParams(ref);
                          offset++;//cursor - (bi << ADDRESS_BITS);+1
                      } else {
                          //offset = 0;//cursor - (bi << ADDRESS_BITS);
                          lastReturn = current[offset++];
                      }
                  } else {
                	 
                      lastReturn += get(ref, offset);
                      offset = (++offset) % BATCH_SIZE;
                  }
                  return true;

			  }
			lastReturn = INVALID;
			return false;
			  
		}

		/**
		 * Get the index of the batch this cursor position falls into
		 * 
		 * @param index
		 * @return
		 */
		private int batchIndex(int index) {
			return index >> ADDRESS_BITS;
		}

		/** Next need be called after skipping.
		 * 
		 */
		@Override
		public boolean skipTo(int target) {

			// NOTE : Update lastReturn. 

			// Skip is lesser than the smallest value in the set, skip to base.
			if (baseList.size() > 0 && target <= baseList.get(0)) 
			{
			    // set batch index to 0
			    bi = 0;
				// set cursor to 0 offset to 1 as if next was called.
				cursor = 0;
				offset = 1;
				compressedSet.updateParams(blob.get(0));
				// set lastReturn as if next was called
				lastReturn = baseList.get(0);
				return true;
			}

			// If the target is outside the compressed space
			if (target >= current[0]) 
			{
			  
				bi = blobSize;
				ref = null;
				
				offset = findAndUpdate(current, target);
				
				if (offset > 0) {
					cursor = blobSize * BATCH_SIZE + offset-1;
					return true;
				}
				// We have gone over the batch boundary
				else if(offset == 0){
				  cursor = (blobSize+1) * BATCH_SIZE;
				}
				 
				lastReturn = INVALID;
				return false;
			}
			

			// This returns the blob where base value is less than the value looked for.
			int index = binarySearchForNearest(target, 0, blobSize - 1);
			// System.out.println("index:"+index);
			// If we can't find anything we are out of range
			if (index == -1 && target < current[0])
			{
				cursor = blobSize*BATCH_SIZE;
				lastReturn  = current[0];
				offset = 1;
				return true;
			}
			else if(index == -1)
			{
				lastReturn = INVALID;
				return false;
			}
			else {
				blit = baseList.listIterator(index);
				blobit = blob.listIterator(index);

				// Move both these further, as we are in this block, so that
				// doc() call works.
				int baseVal = blit.next();
				ref = blobit.next();
				compressedSet.updateParams(ref);

				if (baseVal == target) {
					
					// this 
					offset = 1;
					lastReturn = target;
					cursor = index * BATCH_SIZE;
				} else 
				{
					// find the nearest integer in the compressed space.

					// printSet(blob.get(index), baseVal);
					offset = findAndUpdate(blob.get(index), target, baseVal);
					// oops we fell into the gap. This case happens when we land
					// in the gap between two batches. We can optimize this
					// step.
					// YOU HAVE TO ADD THE CASE WHERE OFFSET  = 0
					if (offset < 1) 
					{
						
						if(++index < blobSize)
						{
						  lastReturn = baseList.get(index);
						  ref = blob.get(index);
						  compressedSet.updateParams(ref);
						}
						else
						{
						  lastReturn = current[0];
						}
						bi = index;
						offset = 1;
			     		cursor = index*BATCH_SIZE;	
                        return true;
                          
					}
					
					
					// we are in the middle of the compressed space then
					

				}
				cursor = index * BATCH_SIZE + offset-1;
				
				return true;
			}

		}

		private void printSet(MyOpenBitSet test, int base) {
			try {
				int localBase = base;
				for (int i = 1; i < BATCH_SIZE; i++) {
					localBase += compressedSet.get(test, i);
					System.out.print(localBase + ",");
				}
			} catch (Exception e) {
				e.printStackTrace();
				int localBase = base;
				int testint[] = compressedSet.decompress(test);
				for (int i = 1; i < BATCH_SIZE; i++) {
					localBase += testint[i];
					System.out.print(localBase + ",");
				}
			}

		}

		/**
		 * Find the element in the compressed set
		 * 
		 * @param next
		 * @param target
		 * @param base
		 * @return
		 */
		private int findAndUpdate(MyOpenBitSet next, int target, int base) {
			if (target < base)
				return -1;
			if (target == base)
				return 0;
			lastReturn = base;

			for (int i = 1; i < BATCH_SIZE; i++) {
				// System.out.println("Getting "+i);
				// System.out.flush();

				lastReturn += get(next, i);
				if (lastReturn>=target){
				//	if(i==127)
						return (i+1) % BATCH_SIZE;
				}
			}
			return -1;
		}

		/**
		 * Find the element in the set and update parameters.
		 * 
		 */
		private int findAndUpdate(int[] array, int target) {
			
			if (target < current[0])
				return -1;
			// We have moved one over
			if (target == current[0])
				return 1;
			lastReturn = current[0];
			
			
			lastReturn = array[0];

			for (int i = 1; i < current_size; i++) 
			{
			  lastReturn += array[i];
			  
				if (lastReturn>=target && i+1 < current_size) 
				{
					return (i+1) % BATCH_SIZE;
				} 
				else if(lastReturn>=target)
					return BATCH_SIZE;
			}
			return -1;

		}
		
		public int getCursor()
		{
			return cursor;
		}

	}

	public DocIdSetIterator iterator() {
		return new P4DDocIdSetIterator();
	}



	public int find(int val)
	{
	  P4DDocIdSetIterator dcit = new P4DDocIdSetIterator();
	 
	      dcit.skipTo(val);
	      if(dcit.doc() == val)
	        return dcit.getCursor(); 
	      return -1;
	}

	 private int findIn(MyOpenBitSet myOpenBitSet, int baseVal, int val) 
	 {
		return -1;
	 }


	private int findIn(int[] current, Integer baseVal, int val) 
	{
		int local = baseVal;
		for(int i=1;i<BATCH_SIZE;i++)
		{
			local+=current[i];
		
			if(val > local)
			{	
				if(local==val)
				return i;
			}
			else
				return -1;
			
		}
		return -1;
	}



}
